// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: log_factory.cpp 316 2009-01-08 18:01:10Z heavyzheng $
//

#ifndef ZZHENG_LOGGING_SOURCE
#define ZZHENG_LOGGING_SOURCE
#endif // ZZHENG_LOGGING_SOURCE

#include <zzheng/logging/log4cplus/log_factory.hpp>

#if defined(ZZHENG_LOGGING_USES_LOG4CPLUS) || defined(ZZHENG_LOGGING_HAS_LOG4CPLUS)


#include <zzheng/logging/log4cplus/logger.hpp>
#include <zzheng/logging/log4cplus/include_property.hpp>
#include <zzheng/logging/log4cplus/include_configurator.hpp>

#include <zzheng/logging/log_level.hpp>
#include <zzheng/logging/destination.hpp>
#include <zzheng/logging/console.hpp>
#include <zzheng/logging/rolling_file.hpp>

#include <zzheng/utility/boost_filesystem.hpp>
#include <zzheng/utility/sys_env.hpp>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(push)
#  pragma warning(disable: 4511 4512)
#endif // ZZHENG_MSVC

#include <boost/lexical_cast.hpp>

#ifdef ZZHENG_MSVC
#  pragma warning(pop)
#endif // ZZHENG_MSVC

#include <cassert>
#include <string>


namespace zzheng {
namespace logging {
namespace log4cplus_ {

    namespace {

        void configure_layout(log4cplus::helpers::Properties& props, const std::string& prefix) {
            props.setProperty(prefix + ".layout", "log4cplus::PatternLayout");
            props.setProperty(prefix + ".layout.ConversionPattern", "%d [%-4t] [%-5p] %c - %m%n");
        }

        void configure_null_dev(log4cplus::helpers::Properties& props, const std::string& name) {
            const std::string APPENDER_PREFIX = "log4cplus.appender." + name;
            const std::string APPENDER_NAME   = "log4cplus::NullAppender";
            props.setProperty(APPENDER_PREFIX, APPENDER_NAME);
            // No need to configure layout for null appender.
        }

        void configure_console(log4cplus::helpers::Properties& props,
                               const std::string& name,
                               destination& dest) {

            // Wrap the destination.
            console wrapper;
            wrapper.copy_props(dest);

            // Set the log4cplus appender type to console appender.
            const std::string APPENDER_PREFIX = "log4cplus.appender." + name;
            const std::string APPENDER_NAME   = "log4cplus::ConsoleAppender";
            props.setProperty(APPENDER_PREFIX, APPENDER_NAME);

            // Set the log4cplus appender properties.
            props.setProperty(
                APPENDER_PREFIX + ".logToStdErr",
                (wrapper.use_stderr() ? "true" : "false") );
            props.setProperty(
                APPENDER_PREFIX + ".ImmediateFlush",
                (wrapper.immediate_flush() ? "true" : "false") );

            // Configure the layout.
            configure_layout(props, APPENDER_PREFIX);
        }

        void configure_single_file(log4cplus::helpers::Properties& props,
                                   const std::string& name,
                                   destination& dest) {
            // Set the log4cplus appender properties.
            const std::string APPENDER_PREFIX = "log4cplus.appender." + name;
            const std::string APPENDER_NAME   = "log4cplus::FileAppender";
            props.setProperty(APPENDER_PREFIX, APPENDER_NAME);

            // TODO:
            dest;
        }

        void configure_rolling_file(log4cplus::helpers::Properties& props,
                                    const std::string& name,
                                    destination& dest) {
            // Wrap the destination.
            rolling_file wrapper;
            wrapper.copy_props(dest);

            // Get the log directory name, and create it if it does not exist.
            std::string dirname = zzheng::sys::expand_sys_var(wrapper.dirname());
            try {
                boost::filesystem::path dir_path = zzheng::make_path(dirname);
                boost::filesystem::create_directories(dir_path);
            } catch (const boost::filesystem::filesystem_error&) {
                // Ignore the error.
            }

            // Set the log4cplus appender type to rolling file appender.
            const std::string APPENDER_PREFIX = "log4cplus.appender." + name;
            const std::string APPENDER_NAME   = "log4cplus::RollingFileAppender";
            props.setProperty(APPENDER_PREFIX, APPENDER_NAME);

            // Set the log4cplus appender properties.
            props.setProperty(
                APPENDER_PREFIX + ".File",
                zzheng::sys::join_path(dirname, wrapper.basename()) );
            props.setProperty(
                APPENDER_PREFIX + ".MaxFileSize",
                boost::lexical_cast<std::string>(wrapper.max_file_size_kb()) + "KB" );
            props.setProperty(
                APPENDER_PREFIX + ".MaxBackupIndex",
                boost::lexical_cast<std::string>(wrapper.max_backup_index()) );

            // Configure layout.
            configure_layout(props, APPENDER_PREFIX);
        }

    } // anonymous namespace

    ////////////////////////////////////////////////////////////////////////////////////////////////

    log_factory& log_factory::instance() {
        static log_factory instance;
        return instance;
    }

    log_factory::log_factory()
    : hierarchy_(), root_(), threshold_(default_level_number), dests_() {
        // Do nothing.
    }

    log_factory::~log_factory() {
        root_.reset();         // release the root logger first,
        hierarchy_.shutdown(); // and then shut down the logging hierarchy.
    }

    void log_factory::set_threshold(const log_level& threshold) {
        threshold_ = threshold;
    }

    void log_factory::add_destination(const std::string& name, const destination& dest) {
        dests_[name] = dest;
    }

    bool log_factory::configure() {
        static bool configured = false;
        if (!configured) {

            // Fill the log4cplus configuration properties.
            log4cplus::helpers::Properties props;
            std::string root_logger_prop = "INHERITED"; // first value: INHERITED threshold.
            if (dests_.empty()) {
                // When no destination is added, use null_dev_destination.
                const std::string null_dev_name = "null_dev";
                root_logger_prop.append(",").append(null_dev_name);
                configure_null_dev(props, null_dev_name);
            } else {
                // Otherwise, configure all the destinations.
                for (destination_map::iterator i = dests_.begin(); i != dests_.end(); ++i) {
                    switch (i->second.type()) {
                    case null_dev_destination:
                        root_logger_prop.append(",").append(i->first);
                        configure_null_dev(props, i->first);
                        break;
                    case console_destination:
                        root_logger_prop.append(",").append(i->first);
                        configure_console(props, i->first, i->second);
                        break;
                    case single_file_destination:
                        root_logger_prop.append(",").append(i->first);
                        configure_single_file(props, i->first, i->second);
                        break;
                    case rolling_file_destination:
                        root_logger_prop.append(",").append(i->first);
                        configure_rolling_file(props, i->first, i->second);
                        break;
                    default:
                        assert(! "Unknown destination type.");
                        // Unknown destinations are considered as null_dev_destination.
                        root_logger_prop.append(",").append(i->first);
                        configure_null_dev(props, i->first);
                        break;
                    }
                }
            }

            // Set the root logger configuration property.
            props.setProperty("log4cplus.rootLogger", root_logger_prop);

            // Configure the log4cplus hierarchy using configuration properties.
            log4cplus::PropertyConfigurator configurator(props, hierarchy_);
            configurator.configure();
            configured = true;
        }
        return configured;
    }

    boost::weak_ptr<logger> log_factory::get_root() {
        if (configure()) {
            if (root_ == 0) {
                root_.reset(new logger(hierarchy_, threshold_));
            }
            return boost::weak_ptr<logger>(root_);
        } else {
            return boost::weak_ptr<logger>();
        }
    }

} // namespace zzheng::logging::log4cplus_
} // namespace zzheng::logging
} // namespace zzheng


#endif // ZZHENG_LOGGING_USES_LOG4CPLUS || ZZHENG_LOGGING_HAS_LOG4CPLUS



